﻿using System;
using System.Linq;
using System.Windows.Media;
using System.Windows.Shapes;
using System.Windows;
using System.Windows.Controls;
using System.Data;
using System.Windows.Media.Imaging;
using Shrestha.Charts.Info;

namespace Shrestha.Charts.Graph
{
    public class TriGraph
    {

        private readonly IWindow _iw;
        private readonly Runner _r;

        public int VTICKNUM { get; set; }
        public int HTICKNUM { get; set; }
        public double TICKLENGTH { get; set; }
        public int FONTSIZE { get; set; }
        public int FONTPADDING { get; set; }
        public Brush StoryBrush { get; set; }
        public Brush EventBrush { get; set; }
        private System.Drawing.SolidBrush EventFill { get; set; }
        public Brush AxisBrush { get; set; }
        public Brush MetaBrush { get; set; }

        public int GeometryToShapeThreshold { get; set; }

        public TriGraph(IWindow chartWindow, Runner r)
        {
            SetDefault();
            this._iw = chartWindow;
            this._r = r;
            this.GeometryToShapeThreshold = 100;
            //EventFill = new System.Drawing.Brush();
         }

        private void SetDefault()
        {
            VTICKNUM = 12;
            HTICKNUM = 8;
            TICKLENGTH = 5.0;
            FONTSIZE = 10;
            FONTPADDING = 12;
            EventBrush = Brushes.Tomato;
            AxisBrush = Brushes.Black;
            MetaBrush = Brushes.Gray;
        }

        private void DrawAxes()
        {
            DrawLatitudeAxis(_iw.GetLatCanvas());
            DrawLongitudeAxis(_iw.GetLngCanvas());
            DrawTimeAxis(_iw.GetDateCanvas());
        }

        private UIElement GraphText(double x, double y, string text, int textsize, HorizontalAlignment textalign)
        {
            TextBlock textBlock = new TextBlock();
            textBlock.Text = text;
            textBlock.HorizontalAlignment = textalign;
            textBlock.Foreground = AxisBrush;
            textBlock.FontSize = textsize;
            Canvas.SetLeft(textBlock, x);
            Canvas.SetTop(textBlock, y);
            return textBlock;
        }

        private void DrawLatitudeAxis(Canvas c)
        {
            c.Children.Clear();
            double x = c.ActualWidth - TICKLENGTH;
            double increment = _iw.GetDrawingCanvas().ActualHeight / VTICKNUM;
            double tincrement = (_r.Meta.MaxLatitude - _r.Meta.MinLatitude) / VTICKNUM;

            GeometryGroup grp = new GeometryGroup();
            double j = c.ActualHeight;
            double k = _r.Meta.MinLatitude;
            while ((_iw.GetDrawingCanvas().ActualHeight - j) <= _iw.GetDrawingCanvas().ActualHeight)
            {
                grp.Children.Add(new LineGeometry(new Point(x, j), new Point(x + TICKLENGTH, j)));
                c.Children.Add(GraphText(x - 38, j - 8, String.Format("{0:0.0000}", k), FONTSIZE, HorizontalAlignment.Left));
                j -= increment;
                k += tincrement;
            }

            grp.Freeze();
            Path tickPath = new Path()
            {
                Stroke = AxisBrush,
                StrokeThickness = 1.0
            };
            tickPath.Data = grp;
            c.Children.Add(tickPath);
        }

        private void DrawLongitudeAxis(Canvas c)
        {
            c.Children.Clear();
            double x = 0.0;
            double increment = _iw.GetDrawingCanvas().ActualHeight / VTICKNUM;
            double tincrement = (_r.Meta.MaxLongitude - _r.Meta.MinLongitude) / VTICKNUM;

            GeometryGroup grp = new GeometryGroup();
            double j = c.ActualHeight;
            double k = _r.Meta.MinLongitude;
            while ((_iw.GetDrawingCanvas().ActualHeight - j) <= _iw.GetDrawingCanvas().ActualHeight)
            {
                grp.Children.Add(new LineGeometry(new Point(x, j), new Point(x + TICKLENGTH, j)));
                c.Children.Add(GraphText(x + 8, j - 8, String.Format("{0:0.0000}", k), FONTSIZE, HorizontalAlignment.Left));
                j -= increment;
                k += tincrement;
            }

            grp.Freeze();
            Path tickPath = new Path()
            {
                Stroke = AxisBrush,
                StrokeThickness = 1.0
            };
            tickPath.Data = grp;
            c.Children.Add(tickPath);
        }

        private void DrawTimeAxis(Canvas c)
        {
            c.Children.Clear();
            double increment = _iw.GetDrawingCanvas().ActualWidth / HTICKNUM;
            double tincrement = _r.Meta.MaxEventDate.Subtract(_r.Meta.MinEventDate).TotalDays / HTICKNUM;

            GeometryGroup grp = new GeometryGroup();
            double j = 0.0;
            DateTime k = _r.Meta.MinEventDate;
            while (j <= _iw.GetDrawingCanvas().ActualWidth + 1)
            {
                grp.Children.Add(new LineGeometry(new Point(j, 0), new Point(j, TICKLENGTH)));
                c.Children.Add(GraphText(j - 12, 8.0, k.ToString("MM/dd/yy"), FONTSIZE, HorizontalAlignment.Center));
                j += increment;
                k = k.AddDays(tincrement);
            }

            grp.Freeze();
            Path tickPath = new Path()
            {
                Stroke = AxisBrush,
                StrokeThickness = 1.0
            };
            tickPath.Data = grp;
            c.Children.Add(tickPath);
        }

        public void DrawGraph(DataView _dv, DataView _dmv, DrawingManager _dm, bool ShowLocationLines, bool ShowMetaLayer)
        {
            
            _iw.GetDrawingCanvas().Children.Clear();
            _dm.Refresh();
            
            if (ShowLocationLines) DrawLocationLines(_dv, _dmv, _dm);
            if (ShowMetaLayer) DrawMetaLayer(_dv, _dmv, _dm);

            if (_dv.Count < GeometryToShapeThreshold)
                DrawShapePoints(_dv, _dm);
            else
                DrawGeometryPoints(_dv, _dm); 

            DrawAxes();
        }

        public void DrawStory(DataView _dv, DataView _dmv, DrawingManager _dm, bool ShowLocationLines, bool ShowMetaLayer)
        {
            double x, y;
            double lx, ly;
            lx = ly = -1;
            _iw.GetDrawingCanvas().Children.Clear();
            _dm.Refresh();

            if (ShowLocationLines) DrawLocationLines(_dv, _dmv, _dm);
            if (ShowMetaLayer) DrawMetaLayer(_dv, _dmv, _dm);

            var groups = _dv.ToTable().AsEnumerable().GroupBy(r => r.Field<string>(_r.CharacterName).Trim());
#if DEBUG
            int i = 0;
            Legend l = new Legend();
#endif
            foreach (var g in groups)
            {
                String charName = null;
                StoryBrush = null;
                GeometryGroup DrawnEvents = new GeometryGroup();
                
                foreach (DataRow dr in g.CopyToDataTable().AsEnumerable().OrderBy(r=>r.Field<DateTime>(_r.DateTimeName)))
                {
                    if (StoryBrush == null)
                    {
                        Color cx = GetColorFromString((string)dr[_r.CharacterName]);
                        StoryBrush = new SolidColorBrush()
                        {
                            Color = cx
                        };
                    }
                    _dm.GetCanvasCoordinates((double)dr[_r.LatName], (double)dr[_r.LngName], (DateTime)dr[_r.DateTimeName], out x, out y);
                    
                    if(charName==null) charName = (string)dr[_r.CharacterName];

                    if (lx > 0 && ly > 0)
                    {
                        DrawnEvents.Children.Add(new LineGeometry(new Point(lx, ly), new Point(x, y)));
                    }

                    DrawnEvents.Children.Add(new RectangleGeometry(new Rect(x - 2, y - 2, 4, 4)));
                    lx = x;
                    ly = y;
                }
                DrawnEvents.Freeze();

#if DEBUG
                Color col;
                switch (i)
                {
                    case 0:
                        StoryBrush = Brushes.Green;
                        l.Add(new LegendEntry() { color = System.Drawing.Color.Green, misc = charName });
                        break;
                    case 1:
                        StoryBrush = Brushes.IndianRed;
                        l.Add(new LegendEntry() { color = System.Drawing.Color.IndianRed, misc = charName });
                        break;
                    case 2:
                        StoryBrush = Brushes.DarkSlateBlue;
                        l.Add(new LegendEntry() { color = System.Drawing.Color.DarkSlateBlue, misc = charName });
                        break;
                    case 3:
                        StoryBrush = Brushes.OrangeRed;
                        l.Add(new LegendEntry() { color = System.Drawing.Color.OrangeRed, misc = charName });
                        break;
                    case 4:
                        StoryBrush = Brushes.RoyalBlue;
                        l.Add(new LegendEntry() { color = System.Drawing.Color.RoyalBlue, misc = charName });
                        break;
                    case 5:
                        StoryBrush = Brushes.MediumOrchid;
                        l.Add(new LegendEntry() { color = System.Drawing.Color.MediumOrchid, misc = charName });
                        break;
                    case 6:
                        StoryBrush = Brushes.Goldenrod;
                        l.Add(new LegendEntry() { color = System.Drawing.Color.Goldenrod, misc = charName });
                        break;
                    default:
                        col = getBrushFromHash(charName);
                        StoryBrush = new SolidColorBrush(col);
                        l.Add(new LegendEntry() { color = System.Drawing.Color.FromArgb(col.A, col.R, col.G, col.B), misc = charName });
                        break;
                }

                //_r.legend = l;
#endif
                Path p = new Path()
                {
                    Stroke = StoryBrush,
                    StrokeThickness = 1.5,
                    StrokeDashArray = { 8.0, 2.0 },
                    Data = DrawnEvents,
                    ToolTip = g.Key
                };
               
                _iw.GetDrawingCanvas().Children.Add(p);
                lx = ly = -1;
#if DEBUG
                i++;

                charName = null;
#endif
            }
            _r.legend = l;
            DrawAxes();
        }

        private Color GetColorFromString(string str)
        {
            var argb = str.GetHashCode();
            return Color.FromArgb((byte)0xFF,
                              (byte)((argb & 0xFF0000) >> 0x12),
                              (byte)((argb & 0xFF00) >> 0x8),
                              (byte)(argb & 0xCC));
            //return Color.FromArgb((byte)((argb & -16777216) >> 0x18),
            //                  (byte)((argb & 0xFF0000) >> 0x12),
            //                  (byte)((argb & 0x0500) >> 0x8),
            //                  (byte)(argb & 0xC2));
        }

        private String GetDrawingColorFromString(string str)
        {
            var argb = str.GetHashCode();
            return "#"+System.Drawing.Color.FromArgb((byte)((argb & -16777216) >> 0x18),
                              (byte)((argb & 0xFF0000) >> 0x12),
                              (byte)((argb & 0xFF00) >> 0x8),
                              (byte)(argb & 0xFF)).Name.Substring(2);
        }

        private String GetColorStringFromString(string str)
        {
            var argb = str.GetHashCode() & 0x00FFFFFF;
            return "#"+ argb.ToString("X6");
        }

        private string GetColorStringFromInt(int i)
        {
            //double j = 1.0 - (i * 0.1);
            //j = j < 0.0 ? 0.0 : j;
            double j = (i * 0.1);
            j = j >1.0 ? 1.0 : j;
            System.Drawing.Color c1 = System.Drawing.Color.Red;
            System.Drawing.Color c2 = System.Drawing.Color.FromArgb(c1.A,
                (int)(c1.R * j), (int)(c1.G * j), (int)(c1.B * j));
            return "#" + c2.Name.Substring(2);
        }

        private Color GetColorFromInt(int i)
        {
            byte[] g = BitConverter.GetBytes(i);
            return Color.FromArgb(g[3], g[2], g[1], g[0]);
        }


        private void DrawMetaLayer(DataView _dv, DataView _dmv, DrawingManager _dm)
        {
            if (_dmv.Count < GeometryToShapeThreshold)
                DrawShapeMeta(_dmv, _dm);
            else
                DrawGeometryMeta(_dmv, _dm); 
        }

        private void DrawGeometryMeta(DataView _dv, DrawingManager _dm)
        {
            GeometryGroup DrawnLocations = new GeometryGroup();
            double x;
            foreach (DataRow dr in _dv.ToTable().Rows)
            {
                x= _dm.GetXFromEventdate((DateTime)dr[_r.DateTimeName]);
                DrawnLocations.Children.Add(new LineGeometry(new Point(x, 0), new Point(x, _iw.GetDrawingCanvas().ActualHeight)));
            }

            DrawnLocations.Freeze();
            Path locationPath = new Path()
            {
                Stroke = MetaBrush,
                StrokeThickness = 0.8,
                Opacity = 0.3
            };
            locationPath.Data = DrawnLocations;
            _iw.GetDrawingCanvas().Children.Add(locationPath);
        }

        private void DrawShapeMeta(DataView _dmv, DrawingManager _dm)
        {
            foreach (DataRow dr in _dmv.ToTable().Rows)
            {
                double tmx = _dm.GetXFromEventdate((DateTime)dr[_r.DateTimeName]);
                Line l = new Line()
                {
                    X1 = tmx,
                    X2 = tmx,
                    Y1 = 0.0,
                    Y2 = _iw.GetDrawingCanvas().ActualHeight,
                    ToolTip = dr[1].ToString(),
                    Stroke = MetaBrush,
                    StrokeThickness = 0.8,
                    //StrokeDashArray = { 8.0, 2.0 },
                    Opacity = 0.3
                };
                _iw.GetDrawingCanvas().Children.Add(l);
            }
        }

        private void DrawLocationLines(DataView _dv, DataView _dmv, DrawingManager _dm)
        {
            GeometryGroup DrawnLocations = new GeometryGroup();
            //foreach (DataRow dr in _dv.ToTable().Rows)
            //{
            //    DrawnLocations.Children.Add(new LineGeometry(new Point(0, _dm.GetYFromLatitude((double)dr[_r.LatName])), new Point(_iw.GetDrawingCanvas().ActualWidth, _dm.GetYFromLongitude((double)dr[_r.LngName]))));
            //}

            //DrawnLocations.Freeze();
            //Path locationPath = new Path()
            //{
            //    Stroke = Brushes.Gray,
            //    StrokeThickness = 0.5,
            //    Opacity = 0.3
            //};
            //locationPath.Data = DrawnLocations;
            //_iw.GetDrawingCanvas().Children.Add(locationPath);

            LocationLineDrawer ld = new LocationLineDrawer((int)_iw.GetDrawingCanvas().ActualWidth, (int)_iw.GetDrawingCanvas().ActualHeight, _dv, _dm, _r);
            ////ld.DrawParallel();
            ld.Draw();
            Image i = new Image();
            i.Source = GraphUtils.loadBitmap(ld.GetBitmap());
            _iw.GetDrawingCanvas().Children.Add(i);

            ld.Save();
        }

        private void DrawGeometryPoints(DataView _dv, DrawingManager _dm)
        {
            //GeometryGroup DrawnEvents = new GeometryGroup();

            //double x, y;

            //foreach (DataRow dr in _dv.ToTable().Rows)
            //{
            //    _dm.GetCanvasCoordinates((double)dr[_r.LatName], (double)dr[_r.LngName], (DateTime)dr[_r.DateTimeName], out x, out y);
            //    DrawnEvents.Children.Add(new RectangleGeometry(new Rect(x, y, 2, 2)));
            //}

            //DrawnEvents.Freeze();
            //Path p = new Path()
            //{
            //    Stroke = Brushes.Tomato,
            //    Fill = Brushes.Red,
            //    StrokeThickness = 1.0
            //};
            //p.Data = DrawnEvents;
            //_iw.GetDrawingCanvas().Children.Add(p);

            PointsDrawer pd = new PointsDrawer((int)_iw.GetDrawingCanvas().ActualWidth, (int)_iw.GetDrawingCanvas().ActualHeight, _dv, _dm, _r) { OutputFile = "Jarvis.png", Padding = 1 };
            pd.Draw();
            Image i = new Image();
            i.Source = GraphUtils.loadBitmap(pd.GetBitmap());
            _iw.GetDrawingCanvas().Children.Add(i);
        }

        private void DrawShapePoints(DataView _dv, DrawingManager _dm)
        {
            double x, y, ylat, ylng;

            var converter = new System.Windows.Media.BrushConverter();

            foreach (DataRow dr in _dv.ToTable().Rows)
            {
                //int hw = int.Parse(dr[_r.GradientName].ToString());
                //var p = GetColorStringFromInt(hw);
                ////var p = GetDrawingColorFromString(dr[_r.GradientName].ToString());
                //hw = hw > 9 ? 9 : hw;
                //var _b  = (Brush)converter.ConvertFrom(p);
                //string k = dr[_r.GradientName].ToString();
                //if (k.Equals("Civilians")) _b = Brushes.PeachPuff;
                //else if (k.Equals("SecurityForce") || k.Equals("LocalMilitia") || k.Equals("LocalPoliceForce")) _b = Brushes.Red;
                //else if (k.Equals("NGO") || k.Equals("Mbororo")) _b = Brushes.Wheat;
                //else _b = Brushes.DarkRed;

                //int _xs = int.Parse(dr["civ"].ToString());
                //_xs = _xs>15?15:_xs;

                _dm.GetCanvasCoordinates((double)dr[_r.LatName], (double)dr[_r.LngName], (DateTime)dr[_r.DateTimeName], out x, out y, out ylat, out ylng);
                _iw.GetDrawingCanvas().Children.Add(new TriMarker(x, y, dr[_r.LatName].ToString(), dr[_r.LngName].ToString(), dr[_r.DateTimeName].ToString(), this) { MarkerHeight = 4, MarkerWidth = 4, Fill = Brushes.Orange, Stroke = Brushes.Tomato, YLat = ylat, YLng = ylng, Opacity=.8 });

                //if(dr["Clash"].ToString().Equals("Yes"))
                //_iw.GetDrawingCanvas().Children.Add(new TriMarker(x, y, dr[_r.LatName].ToString(), dr[_r.LngName].ToString(), dr[_r.DateTimeName].ToString(), this) { MarkerHeight = 2, MarkerWidth = 2, Fill = Brushes.Blue, Stroke = Brushes.Blue, YLat = ylat, YLng = ylng, Opacity = .5 });
            }
        }

        public void DrawTempLocation(double ylat, double ylng, double x)
        {
            _iw.GetHoverCanvas().Children.Add(new Line() { X1 = 0, Y1 = ylat, X2 = _iw.GetDrawingCanvas().ActualWidth, Y2 = ylng, Stroke = Brushes.SkyBlue, StrokeThickness = 1.0 });
            _iw.GetHoverCanvas().Children.Add(new Line() { X1 = x, Y1 = 0, X2 = x, Y2 = _iw.GetDrawingCanvas().ActualHeight, Stroke = Brushes.SkyBlue, StrokeThickness = 1.0 });
        }

        public void ClearTempLocation()
        {
            _iw.GetHoverCanvas().Children.Clear();
        }

        private void printdebug(object o)
        {
            Console.WriteLine(o.ToString());
        }

        private Color getBrushFromHash(string s)
        {
            Random r = new Random();
            return Color.FromArgb(0xff, (byte)s.GetHashCode(), (byte)r.Next(), (byte)r.Next());
        }
    }
}
